/*
Bench 100k connection and 10k query.

*/

package main

import (
	"database/sql"
	"epg/utils"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/urfave/cli"
)

//db
var (
	dbs       []*sql.DB
	chOK      = make(chan struct{}, 1024)
	chBad     = make(chan struct{}, 1024)
	totalTime = make(chan time.Duration, 1024)
	inRate    func() bool

	ctx *cli.Context
	die = make(chan struct{})
)

func init() {
	dbs = make([]*sql.DB, 0, 1024)
}

func entry(c *cli.Context) error {
	ctx = c
	num := ctx.Int("n")
	fmt.Println("===================begin===========================")

	signle()

	for j := 0; j < (num/10000)+1; j++ {
		db, err := initDB()
		if err != nil {
			return err
		}
		dbs = append(dbs, db)
	}
	sleep := time.Second / time.Duration(1000)
	sql := c.String("query")
	for i := 1; i <= num; i++ {
		// fmt.Println("create query...", i)
		time.Sleep(sleep)
		go queryDB(sql, i)
	}
	var ok int
	var bad int
	var t = time.Now()
	var tk = time.NewTicker(5 * time.Second)
	var cost time.Duration
LOOP:
	for {
		select {
		case <-die:
			break LOOP
		case <-chOK:
			ok++
		case <-chBad:
			bad++
		case co := <-totalTime:
			cost += co
		case <-tk.C:

			open := ""
			total := 0
			for i, db := range dbs {
				n := db.Stats().OpenConnections
				open += fmt.Sprintf("%v:%v, ", i, n)
				total += n
			}
			fmt.Println("=================")
			fmt.Println("Cost:", time.Now().Sub(t), "OK:", ok, "Bad:", bad, "Turn:", (ok+bad)/num, "dbOpen:", total)
			fmt.Println("Open:", open)
			// fmt.Println("Cost:", time.Now().Sub(t), "Arg:", time.Duration(cost.Nanoseconds()/int64(ok+bad)))
			// fmt.Println("Turn:", (ok+bad)/num)
		}
	}
	for _, db := range dbs {
		db.Close()
	}
	return nil
}

func signle() {
	go func() {
		defer utils.PrintPanicStack()
		sc := make(chan os.Signal, 1)
		signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
		for {
			sig := <-sc
			switch sig {
			case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM:
				fmt.Printf("Got signal [%v] to exit.", sig)
				close(die)
			}
		}
	}()
}

func initDB() (db *sql.DB, err error) {
	num := ctx.Int("n")
	host := ctx.String("host")
	port := ctx.String("port")
	user := ctx.String("user")
	passwd := ctx.String("passwd")
	database := ctx.String("db")
	//DSN root:jumeiops@tcp(127.0.0.1:6603)/sbtest
	db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?readTimeout=10s", user, passwd, host, port, database))
	if err != nil {
		return nil, err
	}
	db.SetMaxIdleConns(num)
	db.SetMaxOpenConns(num)
	inRate = rate(ctx.Int("c"), num)
	return db, nil
}

func queryDB(querySql string, i int) {
	defer func() {
		if x := recover(); x != nil {
			panic(x)
		}
	}()
	l := len(dbs)
	pos := i % l
	db := dbs[pos]
	for {
		if inRate() {
			t1 := time.Now()
			querySql = strings.Replace(querySql, "#{id}", string(i), -1)
			//fmt.Println(querySql)
			rows, err := db.Query(querySql)
			if err != nil {
				log.Println("err:", err)
				chBad <- struct{}{}
				totalTime <- time.Now().Sub(t1)
				continue
			}
			col, err := rows.Columns()
			values := make([]sql.RawBytes, len(col))
			scanArgs := make([]interface{}, len(col))
			for i := range values {
				scanArgs[i] = &values[i]
			}
			if err != nil {
				log.Println("err:", err)
				return
			}
			for rows.Next() {
				if err := rows.Scan(scanArgs...); err != nil {
					log.Println(err)
					return
				}
				//OK
			}

			//OK
			rows.Close()
			totalTime <- time.Now().Sub(t1)
		} else {
			db.Ping()
			<-time.After(time.Duration(ctx.Int("freq")) * time.Millisecond)
		}
		chOK <- struct{}{}
	}
}

func rate(c, n int) func() bool {
	rand.Seed(time.Now().Unix())
	return func() bool {
		r := rand.Intn(n)
		if r > c {
			return false
		}
		return true
	}
}
